/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.drawee.backends.pipeline;

import java.util.concurrent.Executor;

import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;

import com.facebook.common.internal.Objects;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Supplier;
import com.facebook.common.logging.FLog;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawable.base.DrawableWithCaches;
import com.facebook.drawee.components.DeferredReleaser;
import com.facebook.drawee.controller.AbstractDraweeController;
import com.facebook.drawee.interfaces.SettableDraweeHierarchy;
import com.facebook.imagepipeline.animated.factory.AnimatedDrawableFactory;
import com.facebook.imagepipeline.image.CloseableAnimatedImage;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.CloseableStaticBitmap;
import com.facebook.imagepipeline.image.ImageInfo;

/**
 * Drawee controller that bridges the image pipeline with
 * {@link SettableDraweeHierarchy}.
 * <p>
 * The hierarchy's actual image is set to the image(s) obtained by the provided
 * data source. The data source is automatically obtained and closed based on
 * attach / detach calls.
 */
public class PipelineDraweeController extends AbstractDraweeController<CloseableReference<CloseableImage>, ImageInfo> {

	private static final Class<?> TAG = PipelineDraweeController.class;

	// Components
	private final Resources mResources;
	private final AnimatedDrawableFactory mAnimatedDrawableFactory;

	// Constant state (non-final because controllers can be reused)
	private Supplier<DataSource<CloseableReference<CloseableImage>>> mDataSourceSupplier;

	public PipelineDraweeController(Resources resources, DeferredReleaser deferredReleaser,
			AnimatedDrawableFactory animatedDrawableFactory, Executor uiThreadExecutor,
			Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier, String id, Object callerContext) {
		super(deferredReleaser, uiThreadExecutor, id, callerContext);
		mResources = resources;
		mAnimatedDrawableFactory = animatedDrawableFactory;
		init(dataSourceSupplier);
	}

	/**
	 * Initializes this controller with the new data source supplier, id and
	 * caller context. This allows for reusing of the existing controller
	 * instead of instantiating a new one. This method should be called when the
	 * controller is in detached state.
	 * 
	 * @param dataSourceSupplier
	 *            data source supplier
	 * @param id
	 *            unique id for this controller
	 * @param callerContext
	 *            tag and context for this controller
	 */
	public void initialize(Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier, String id,
			Object callerContext) {
		super.initialize(id, callerContext);
		init(dataSourceSupplier);
	}

	private void init(Supplier<DataSource<CloseableReference<CloseableImage>>> dataSourceSupplier) {
		mDataSourceSupplier = dataSourceSupplier;
	}

	protected Resources getResources() {
		return mResources;
	}

	@Override
	protected DataSource<CloseableReference<CloseableImage>> getDataSource() {
		if (FLog.isLoggable(FLog.VERBOSE)) {
			FLog.v(TAG, "controller %x: getDataSource", System.identityHashCode(this));
		}
		return mDataSourceSupplier.get();
	}

	@Override
	protected Drawable createDrawable(CloseableReference<CloseableImage> image) {
		Preconditions.checkState(CloseableReference.isValid(image));
		CloseableImage closeableImage = image.get();
		if (closeableImage instanceof CloseableStaticBitmap) {
			CloseableStaticBitmap closeableStaticBitmap = (CloseableStaticBitmap) closeableImage;
			return new BitmapDrawable(mResources, closeableStaticBitmap.getUnderlyingBitmap());
		} else if (closeableImage instanceof CloseableAnimatedImage) {
			return mAnimatedDrawableFactory.create(((CloseableAnimatedImage) closeableImage).getImageResult());
		} else {
			throw new UnsupportedOperationException("Unrecognized image class: " + closeableImage);
		}
	}

	@Override
	protected ImageInfo getImageInfo(CloseableReference<CloseableImage> image) {
		Preconditions.checkState(CloseableReference.isValid(image));
		return image.get();
	}

	@Override
	protected int getImageHash(@Nullable CloseableReference<CloseableImage> image) {
		return (image != null) ? image.getValueHash() : 0;
	}

	@Override
	protected void releaseImage(@Nullable CloseableReference<CloseableImage> image) {
		CloseableReference.closeSafely(image);
	}

	@Override
	protected void releaseDrawable(@Nullable Drawable drawable) {
		if (drawable instanceof DrawableWithCaches) {
			((DrawableWithCaches) drawable).dropCaches();
		}
	}

	@Override
	public String toString() {
		return Objects.toStringHelper(this).add("super", super.toString())
				.add("dataSourceSupplier", mDataSourceSupplier).toString();
	}
}
